package com.yf.serviceImpl;

import com.yf.bean.CollectionBean;
import com.yf.bean.HistoryAnswer;
import com.yf.service.RedisService;
import com.yf.utils.Utils;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.*;

class JedisPoolHolder {
    private static JedisPool pool = null;
    private JedisPoolHolder() {}
    public static JedisPool getJedisPool() {
        if (pool == null) {
            synchronized (JedisPoolHolder.class) {
                if (pool == null) {
//                    pool = new JedisPool("localhost", 6379);
                    pool = new JedisPool("127.0.0.1", 6379);
//                    pool = new JedisPool("172.22.75.147", 6379);
                }
            }
        }
        return pool;
    }
}

@Log4j2
@Service("RedisServiceImpl")
public class RedisServiceImpl implements RedisService {
//    @Value("${custom.redis.db.regCode}")
//    int redisRegCodeDb;

    private static final JedisPool jedisPool = JedisPoolHolder.getJedisPool();
    private static final String commentLikeSuffix = "%0";
    private static final String commentDislikeSuffix = "%1";
    private static final String commentLikeSetSuffix = "%2";
    private static final String commentDislikeSetSuffix = "%3";

    private static final String userInfoSuffix = "#";
    private static final String regCodeKey = "regCode";
    private static final String regCodeKeyValid = "regCodeValid";
//    private static final String logSessionKey = "logSession";
    private static final long logSessionExpireTime = 3600 * 12;
    private static final long commentLikeExpireTime = 3600 * 12;
    private static final long userHistoryAnswerExpireTime = 3600 * 12;
    // 全站排名
    private static final String ranks = "ranks";
    // 每个用户的排名
//    private static final String user_ranks = "user_ranks";
    private static final String[] subject = {"CS", "OS", "NET", "CCP"};

    private static final String userHistoryAnswerSuffix = "#h";
    private static final String userCollectionSuffix = "#c";

    // subject category list
    private static final String subjectCateSuffix = "_cate";
    // category data `subject_cate` -> list
    private static final String subjectStatistic = "subStat";


    @Override
    public List<String> getSubjectCateV2(String subject) {
        if ("SEQ".equals(subject)) {
            try(Jedis jedis = jedisPool.getResource()) {
                final Set<String> years = jedis.keys("seq_*");
                List<String> ret = new ArrayList<>(years.size());
                for (String y: years) {
                    ret.add(y.substring(4));
                }
                return ret;
            } catch (Exception e) {
                log.error(e);
            }
            return new ArrayList<>(0);
        }

        String key = subject + subjectCateSuffix;
        try(Jedis jedis = jedisPool.getResource()) {
            final String v = jedis.get(key);
            if (v != null && v.length() > 0) {
                return Arrays.asList(v.split("#"));
            }
        } catch (Exception e) {
            log.error(e);
        }
        return new ArrayList<>(0);
    }

    @Override
    public List<String> getSubjectCateIds(String subject, String cate, int start, int count) {
        int end = count == -1 ? -1 : start + count - 1;
        if ("SEQ".equals(subject)) {
            String key = String.format("seq_%s", cate);
            try(Jedis jedis = jedisPool.getResource()) {
                return jedis.lrange(key, start, end);
            } catch (Exception e) {
                log.error(e);
            }
        } else if ("FUN".equals(subject)) {
            try(Jedis jedis = jedisPool.getResource()) {
                return jedis.lrange(cate, start, end);
            } catch (Exception e) {
                log.error(e);
            }
        } else {
            String key = String.format("id_%s_%s", subject, cate);
            try(Jedis jedis = jedisPool.getResource()) {
                return jedis.lrange(key, start, end);
            } catch (Exception e) {
                log.error(e);
            }
        }

        return new ArrayList<>(0);
    }

    @Override
    public int getSubjectCateLen(String subject, String cate) {
        String key;
        if (subject.equals("SEQ")) {
            key = String.format("seq_%s", cate);
        } else if ("FUN".equals(subject)){
            key = cate;
        } else {
            key = String.format("id_%s_%s", subject, cate);
        }
        try(Jedis jedis = jedisPool.getResource()) {
            return (int) jedis.llen(key);
        } catch (Exception e) {
            log.error(e);
        }
        return 0;
    }

    @Override
    public int genRegCode() {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(Jedis jedis = pool.getResource()) {
            String[] codes = new String[100];
            for (int i = 0; i < 20; i++) {
                codes[i] = Utils.genRegCode();
            }
            return (int) jedis.rpush(regCodeKey, codes);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return 0;
    }

    @Override
    public boolean containsRegCode(String token) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(Jedis jedis = pool.getResource()) {
            return jedis.sismember(regCodeKeyValid, token);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return false;
    }

    @Override
    public boolean validateRegCode(String token) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(Jedis jedis = pool.getResource()) {
            return jedis.srem(regCodeKeyValid, token) == 1L;
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return false;
    }

    @Override
    public String getRegCode() {
        String ret;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(Jedis jedis = pool.getResource()) {
            ret = Utils.genRegCode();
            jedis.sadd(regCodeKeyValid, ret);
        } catch (Exception ignore) {
            ret = null;
        }
        return ret;
    }

    @Override
    public boolean cacheUserInfo(String uid, String key, String value) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        long ret = -1;
        try(Jedis jedis = pool.getResource()) {
            boolean notExist = true;
            if (jedis.exists(uid)) {
                notExist = false;
            }
            ret = jedis.hset(uid, key, value);
            if (notExist) {
                jedis.expire(uid, logSessionExpireTime);
            }
        } catch (Exception e) {
            log.error(e);
        }
        return ret >= 0;
    }

    @Override
    public String getUserInfo(String uid, String key) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        String ret = null;
        try(Jedis jedis = pool.getResource()) {
            ret = jedis.hget(uid, key);
        } catch (Exception e) {
            log.error(e);
        }
        return ret;
    }


    public boolean addLoginSession(String uid, String value) {
        return cacheUserInfo(uid, "UA", value);
    }

    public boolean checkLogStatus(String uid, String value) {
        String v = getUserInfo(uid, "UA");
        return v != null && v.equals(value);
    }

    @Override
    public Map<String, String> getCategoryId(String cate) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        Map<String, String> ret = null;
        int k = Utils.getRandInt();
        try (Jedis jedis = pool.getResource()) {
            ret = jedis.hgetAll(cate);
        } catch (Exception e) {
            log.error(e);
        }
        if (ret != null && ret.size() != 0) {
            for (Map.Entry<String, String> entry: ret.entrySet()) {
                final String key = entry.getKey();
                final String value = entry.getValue();
                final byte[] bytes = Utils.mixData(k, 0, value);
                ret.put(key, Base64.encodeBase64String(bytes));
            }
            ret.put("lk", Utils.encodeInt(k));
        }
        return ret;
    }

    @Override
    public List<String> getRanking() {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        List<String> ans = null;
        try (Jedis jedis = pool.getResource()) {
            ans = jedis.lrange(ranks, 0,9);
        } catch (Exception e) {
            log.error(e);
        }
        return ans;
    }

    @Override
    public Integer getQusTotalNum() {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try (Jedis jedis = pool.getResource()) {
            final String qusTotal = jedis.get("QUESTION_NUM");
            if (qusTotal == null) {
                return null;
            }
            return Integer.valueOf(qusTotal);
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

    @Override
    public boolean cacheUserInfoLong(Integer id, String key, String value) {
        String uid = id + userInfoSuffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        boolean ret = false;
        try (Jedis jedis = pool.getResource()) {
            final long l = jedis.hset(uid, key, value);
            ret = l >= 0;
        } catch (Exception ignore) {
            //
        }
        return ret;
    }


    @Override
    public String getUserInfoLong(Integer id, String key) {
        String uid = id + userInfoSuffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try (Jedis jedis = pool.getResource()) {
            return jedis.hget(uid, key);
        } catch (Exception ignore) {
            //
        }
        return null;
    }

    @Override
    public Map<String, Object> getSubjectQusInfo() {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        Map<String, Object> ret = new HashMap<>(4);
        try (Jedis jedis = pool.getResource()) {
            for (String s: subject) {
                String num = jedis.hget(subjectStatistic, String.format("%s_num", s));
                String right = jedis.hget(subjectStatistic, String.format("%s_right", s));
                String wrong = jedis.hget(subjectStatistic, String.format("%s_wrong", s));
                ret.put(s, String.format("%s,%s,%s", num, right, wrong));
            }
        } catch (Exception ignore) {
            //
        }
        return ret;
    }

    @Override
    public List<String> getSubjectCate(String subject) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        List<String> ret;
        try (Jedis jedis = pool.getResource()) {
            Set<String> cate = jedis.hkeys(subject);
            ret = new ArrayList<>(cate);
        } catch (Exception e) {
            log.error(e);
            ret = new ArrayList<>(0);
        }
        return ret;
    }

    @Override
    public String getSubjectAllQusID(String subject) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try (Jedis jedis = pool.getResource()) {
            return jedis.hget("RAND", subject);
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

    @Override
    public String getSubjectAllQusIDv2(String subject) {
        try (Jedis jedis = jedisPool.getResource()) {
            final String cateString = jedis.get(subject + subjectCateSuffix);
            if (cateString == null || cateString.length() == 0) {
                return "";
            }
            String[] cate = cateString.split("#");

            List<String> ret = new ArrayList<>(cate.length);
            for (String c: cate) {
                String k = String.format("id_%s_%s", subject, c);
                final List<String> ids = jedis.lrange(k, 0, -1);
                ret.add(String.join(".", ids));
            }
            return String.join(".", ret);
        } catch (Exception e) {
            log.error(e);
        }
        return "";
    }

    @Override
    public String getSubjectCateId(String subject, String cate) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try (Jedis jedis = pool.getResource()) {
            return jedis.hget(subject, cate);
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }


    boolean setCommentInfo(Integer id, Integer count, String suffix) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        String key = id + suffix;
        if (count == null) {
            count = 0;
        }
        try(Jedis jedis = pool.getResource()) {
            final String ret = jedis.set(key, count.toString());
            jedis.expire(key, commentLikeExpireTime);
            return ret.equals("OK");
        } catch (Exception e) {
            log.error(e);
        }
        return false;
    }

    @Override
    public boolean addLike(Integer id, Integer count) {
        return setCommentInfo(id, count, commentLikeSuffix);
    }

    @Override
    public boolean addDisLike(Integer id, Integer count) {
        return setCommentInfo(id, count, commentDislikeSuffix);
    }

    boolean opLikeData(Integer id, int op, String suffix) {
        JedisPool pool = JedisPoolHolder.getJedisPool();
        String key = id + suffix;
        try(Jedis jedis = pool.getResource()) {
            if (op == 1) {
                jedis.incr(key);
                return true;
            } else if (op == -1) {
                jedis.decr(key);
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            log.error(e);
        }
        return false;
    }
    @Override
    public boolean incLike(Integer id) {
        return opLikeData(id, 1, commentLikeSuffix);
    }

    @Override
    public boolean incDisLike(Integer id) {
        return opLikeData(id, 1, commentDislikeSuffix);
    }

    @Override
    public boolean decLike(Integer id) {
        return opLikeData(id, -1, commentLikeSuffix);
    }

    @Override
    public boolean decDislike(Integer id) {
        return opLikeData(id, -1, commentDislikeSuffix);
    }

    Integer getCommentLikeAndDisLike(Integer id, String suffix) {
        String key = id + suffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(final Jedis jedis = pool.getResource()) {
            final String s = jedis.get(key);
            if (s == null) {
                return -1;
            }
            return Integer.parseInt(s);
        } catch (Exception e) {
            log.error(e);
        }
        return -1;
    }

    @Override
    public Integer getLike(Integer id) {
        return getCommentLikeAndDisLike(id, commentLikeSuffix);
    }

    @Override
    public Integer getDisLike(Integer id) {
        return getCommentLikeAndDisLike(id, commentDislikeSuffix);
    }


    boolean checkLikeContain(Integer cid, Integer uid, String suffix) {
        String key = cid + suffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(final Jedis jedis = pool.getResource()) {
            return jedis.sismember(key, uid.toString());
        } catch (Exception e) {
            log.error(e);
        }
        return false;
    }

    @Override
    public boolean likeContains(Integer cid, Integer uid) {
        return checkLikeContain(cid, uid, commentLikeSetSuffix);
    }

    @Override
    public boolean dislikeContains(Integer cid, Integer uid) {
        return checkLikeContain(cid, uid, commentDislikeSetSuffix);
    }

    boolean checkLikeContainAdd(Integer cid, Integer uid, String suffix) {
        String key = cid + suffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(final Jedis jedis = pool.getResource()) {
            boolean ret = jedis.sadd(key, uid.toString()) > 0L;
            jedis.expire(key, commentLikeExpireTime);
            return ret;
        } catch (Exception e) {
            log.error(e);
        }
        return false;
    }

    @Override
    public boolean likeContainsAdd(Integer cid, Integer uid) {
        return checkLikeContainAdd(cid, uid, commentLikeSetSuffix);
    }

    @Override
    public boolean dislikeContainsAdd(Integer cid, Integer uid) {
        return checkLikeContainAdd(cid, uid, commentDislikeSetSuffix);
    }

    @Override
    public Map<String, String> getUserHistoryAnswer(Integer uid, String prefix) {
        String key = prefix + "#" + uid + userHistoryAnswerSuffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(final Jedis jedis = pool.getResource()) {
            return jedis.hgetAll(key);
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

    @Override
    public Boolean setUserHistoryAnswer(Integer uid, List<HistoryAnswer> bean, String prefix, boolean init) {
        String key = prefix + "#" + uid + userHistoryAnswerSuffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(final Jedis jedis = pool.getResource()) {
            boolean exists = jedis.exists(key);
            for (HistoryAnswer a: bean) {
                String pid = a.getPid().toString();
                String choice = a.getChoice();
                long t = a.getCreated().getTime() / 1000;
                String v;
                if (init) {
                    v = String.format("%s,%s,%s,0", choice, t, prefix);
                } else if (jedis.hexists(key, pid)) {
                    v = String.format("%s,%s,%s,1", choice, t, prefix);
                } else {
                    v = String.format("%s,%s,%s,2", choice, t, prefix);
                }
                jedis.hset(key, pid, v);
            }
            if (!exists) {
                jedis.expire(key, userHistoryAnswerExpireTime);
            }
        } catch (Exception e) {
            log.error(e);
            return false;
        }
        return true;
    }

    @Override
    public Map<String, String> getUserCollection(Integer uid) {
        String key = uid + userCollectionSuffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(final Jedis jedis = pool.getResource()) {
            return jedis.hgetAll(key);
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

    @Override
    public Boolean setUserCollection(Integer uid, List<CollectionBean> bean, boolean init) {
        String key = uid + userCollectionSuffix;
        JedisPool pool = JedisPoolHolder.getJedisPool();
        try(final Jedis jedis = pool.getResource()) {
            boolean exists = jedis.exists(key);
            for (CollectionBean a: bean) {
                String id = a.getId().toString();
                String pid = a.getPid().toString();
                long t = a.getCreated().getTime();
                String v;
                if (init) {
                    v = String.format("%s,%s,0", t, id);
                } else if (jedis.hexists(key, pid)) {
                    v = String.format("%s,%s,1", t, id);
                } else {
                    v = String.format("%s,%s,2", t, id);
                }
                jedis.hset(key, pid, v);
            }
            if (!exists) {
                jedis.expire(key, userHistoryAnswerExpireTime);
            }
        } catch (Exception e) {
            log.error(e);
            return false;
        }
        return true;
    }

    @Override
    public boolean setKey(String key, String value, long expire) {
        try(final Jedis jedis = jedisPool.getResource()) {
            jedis.set(key, value);
            if (expire != 0) {
                jedis.expire(key, expire);
            }
            return true;
        } catch (Exception e) {
            log.error(e);
        }
        return false;
    }

    @Override
    public String getKey(String key) {
        try(final Jedis jedis = jedisPool.getResource()) {
            return jedis.get(key);
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

    @Override
    public boolean existKey(String key) {
        try(final Jedis jedis = jedisPool.getResource()) {
            return jedis.exists(key);
        } catch (Exception e) {
            log.error(e);
        }
        return false;
    }
}
